﻿///<FileHeader>
///     <FileName>TypeDescriptor.cs</FileName>
///     <Author>Kiran Chand Palakkattiri</Author>
///     <Project>
///         <Name>Property Visibility Extensions for DSL</Name>
///         <Url>http://kiranchand.codeplex.com</Url>
///     </Project>
///     <CreationDate>August 02, 2009.</CreationDate>
///     <Contents>
///         <Content type="class" name="ModelTypeDescriptor"
///             baseclass="ElementTypeDescriptor"
///             comments="Extended type descriptor for DSL model elements supports dynamic property visibility behavior."/>
///     </Contents>
///     <Dependencies>
///         <Dependency type="reference" namespace="System" />
///         <Dependency type="reference" namespace="System.Collections.Generic" />
///         <Dependency type="reference" namespace="System.ComponentModel" />
///         <Dependency type="reference" namespace="System.Linq" />
///         <Dependency type="reference" namespace="Microsoft.VisualStudio.Modeling"
///             assembly="Microsoft.VisualStudio.Modeling.Sdk" version="9.0.0.0" prerequisites="Visual Studio SDK 1.1"/>
///         <Dependency type="reference" namespace="Microsoft.VisualStudio.Modeling.Design"
///             assembly="Microsoft.VisualStudio.Modeling.Sdk" version="9.0.0.0" prerequisites="Visual Studio SDK 1.1" />
///     </Dependencies>
///     <Modifications>
///         <Modification changeset="1001" date="August 03, 2009" author="Kiran Chand Palakkattiri">
///             <Target="function" targetname="GetProperties(....)" type="update"
///                 comment="fixed the issues with multiple dependency for visibility behavior of single property."/>
///             <Target="function" targetname="GetPropertyMode(....)" type="create"
///                 comment="fixed the issues with multiple dependency for visibility behavior of single property."/>
///             <Target="function" targetname="UpdatePropertyDescriptor(....)" type="deleted"
///                 comment="fixed the issues with multiple dependency for visibility behavior of single property."/>
///         </Modification>
///         <Modification changeset="1004" date="September 01, 2009" author="Kiran Chand Palakkattiri">
///             <Target="function" targetname="CreateDescriptor(....)" type="update"
///                 comment="fixed the issues custom editor in read only mode; disabled creating new type descriptor for hidden properties"/>
///             <Target="function" targetname="GetProperties(....)" type="update"
///                 comment="null validation included for individual descriptors before updated descriptor collection returned"/>
///         </Modification>
///     </Modifications>
///     <Version>3.0.0.0</Version>
///</FileHeader>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Design;

namespace Kiranchand.Dsl.Extensions.PropertyVisibility
{
    /// <summary>
    /// Extended type descriptor for DSL model elements supports dynamic property visibility behavior.
    /// </summary>
    public sealed class ModelTypeDescriptor : ElementTypeDescriptor
    {
        /// <summary>
        /// Holds the default descriptors for the type from the base class.
        /// </summary>
        private PropertyDescriptorCollection DefaultDescriptors { get; set; }

        /// <summary>
        /// Holds the domain properties of the current model standardValueProvider instance.
        /// </summary>
        private List<DomainPropertyInfo> DomainProperties { get; set; }

        /// <summary>
        /// Holds the instances of the PropertyBehavior attributes.
        /// </summary>
        private List<PropertyBehaviorAttribute> PropertyBehaviors { get; set; }

        /// <summary>
        /// Holds the model elemnt instance
        /// </summary>
        private new ModelElement ModelElement { get; set; }

        /// <summary>
        /// Locking! Not coded
        /// </summary>
        private object syncRoot = new object();

        /// <summary>
        /// Instantiates the instance of  ModelTypeDescriptor and intialize the states of variables.
        /// </summary>
        /// <param name="selectedElement">instance of the selected model standardValueProvider</param>
        public ModelTypeDescriptor(ModelElement selectedElement)
            : base(selectedElement)
        {
            object[] propertyBehaviors = null;
            if (selectedElement == null)
                throw new ArgumentNullException();
            else
            {
                // populate properties
                this.ModelElement = selectedElement;
                this.DomainProperties = this.ModelElement.GetDomainClass().AllDomainProperties.ToList();

                this.PropertyBehaviors = new List<PropertyBehaviorAttribute>();

                propertyBehaviors = this.ModelElement.GetType()
                    .GetCustomAttributes(typeof(PropertyBehaviorAttribute), true);

                if (propertyBehaviors != null && propertyBehaviors.Length > 0)
                {
                    foreach (PropertyBehaviorAttribute oBehavior in propertyBehaviors)
                    {
                        this.PropertyBehaviors.Add(oBehavior);
                    }
                }
                propertyBehaviors = null;
            }
        }
        /// <summary>
        /// Returns a collection of domain property descriptors that contains all the properties of the described type.
        /// </summary>
        /// <returns>The collection of domain property descriptors that contains all the properties of the described type.</returns>
        public override PropertyDescriptorCollection GetProperties()
        {
            lock (syncRoot)
            {
                return this.GetProperties(new Attribute[0] { });
            }
        }

        /// <summary>
        /// Returns a collection of domain property descriptors that contains all the
        /// properties of the described type, filtered by the specified attributes.
        /// </summary>
        /// <param name="attributes">The array of custom attributes on which to filter.</param>
        /// <returns>The collection of domain property descriptors that contains all the properties
        /// of the described type, filtered by the specified attributes.</returns>
        public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            PropertyDescriptorCollection colUpdatedDescriptors = null;      // holds the property descriptors for returning
            List<PropertyBehaviorAttribute> colMatchingBehaviors = null;    // holds a collection of behaviors matching the property
            List<PropertyBehaviorAttribute> colBehaviorCache = null;        // holds the behavior cache for iteration

            // get the default descriptors
            this.DefaultDescriptors = base.GetProperties(attributes);

            // return default descriptors on error
            if (this.ModelElement == null
                || PropertyBehaviors == null
                || PropertyBehaviors.Count <= 0
                || this.DomainProperties == null
                || this.DomainProperties.Count <= 0)
                return this.DefaultDescriptors;

            // instantiating the collection
            colUpdatedDescriptors = new PropertyDescriptorCollection(null);

            // copy the default descriptors
            foreach (PropertyDescriptor oDescriptor in this.DefaultDescriptors)
                colUpdatedDescriptors.Add(oDescriptor);

            // instantiating the behavior cache collection
            colBehaviorCache = new List<PropertyBehaviorAttribute>();
            // copy behaviors
            foreach (PropertyBehaviorAttribute oBehavior in this.PropertyBehaviors)
                colBehaviorCache.Add(oBehavior);
            // iterate for behavior matches
            for (int i = 0; i < this.DomainProperties.Count; i++)
            {
                // behaviors are processed, fine, lets finish off!
                if (colBehaviorCache == null
                    && colBehaviorCache.Count < 0)
                    break;

                // Create temportary storage for matching behaviors
                colMatchingBehaviors = new List<PropertyBehaviorAttribute>();

                // calling by index from a collection is a bit confusing for me! ;)
                DomainPropertyInfo oPropertyInfo = this.DomainProperties[i];

                // I love for loop to foreach!
                for (int j = 0; j < colBehaviorCache.Count; j++)
                {
                    // calling by index from a collection is a bit confusing for me! ;)
                    PropertyBehaviorAttribute oBehavior = colBehaviorCache[j];

                    // check for match. if matching add it to the collection
                    if (oBehavior != null
                        && oPropertyInfo != null
                        && oBehavior.CompareProperty(oPropertyInfo))
                    {
                        colMatchingBehaviors.Add(oBehavior);
                    }

                    // a bit of clean up
                    oBehavior = null;
                }

                // process matching
                if (colMatchingBehaviors != null && colMatchingBehaviors.Count > 0)
                {
                    int nDefault = 0;   // holds the number of default edit mode behavior
                    int nEditable = 0;  // holds the number of editable edit mode behavior
                    int nReadOnly = 0;  // holds the number of read only edit mode behavior
                    int nHidden = 0;    // holds the number of hidden edit mode behavior    
                    DisplayMode mode = DisplayMode.Default; // holds the display mode after each processing

                    for (int j = 0; j < colMatchingBehaviors.Count; j++)
                    {
                        PropertyBehaviorAttribute oBehavior = colMatchingBehaviors[j];
                        if (oBehavior != null && oPropertyInfo != null)
                        {
                            mode = this.GetPropertyMode(oBehavior);
                            // update the counter if the behavior is hidden
                            if (mode == DisplayMode.Hidden)
                            {
                                // hide the grid if one is hidden
                                // please note that we need to reset the properties cache later!
                                nHidden++;
                                break;
                            }
                            // update the counter if behavior is readonly
                            else if (mode == DisplayMode.ReadOnly) nReadOnly++;
                            // update the counter if behavior is editable
                            else if (mode == DisplayMode.Editable) nEditable++;
                            // update the counter if behavior is default
                            else if (mode == DisplayMode.Default) nDefault++;
                            if (colBehaviorCache.Contains(oBehavior))
                                colBehaviorCache.Remove(oBehavior);
                        }
                        oBehavior = null;
                    }

                    // set the display mode
                    ///<remark>Priorities: Hidden = High; ReadOnly = Medium; Editable = Normal; None = Least.</remark>
                    if (nHidden > 0)
                    {
                        mode = DisplayMode.Hidden;
                        // clean up cache!
                        foreach (PropertyBehaviorAttribute oBehavior in colMatchingBehaviors)
                            if (colBehaviorCache.Contains(oBehavior))
                                colBehaviorCache.Remove(oBehavior);
                    }
                    else if (nReadOnly > 0)
                        mode = DisplayMode.ReadOnly;
                    else if (nEditable > 0)
                        mode = DisplayMode.Editable;
                    else
                        mode = DisplayMode.Default;

                    // reset the visibility if not default mode
                    if (mode != DisplayMode.Default)
                    {
                        // get the default descriptor
                        PropertyDescriptor oDescriptor = this.DefaultDescriptors.Find(oPropertyInfo.Name, true);

                        // remove the descriptor from updated descriptor collecton
                        if (oDescriptor != null && colUpdatedDescriptors.Contains(oDescriptor))
                            colUpdatedDescriptors.Remove(oDescriptor);

                        // create a new descriptor
                        oDescriptor = this.CreateDescriptor(oPropertyInfo, mode);

                        // add it to the updated descriptor collecton
                        if (oDescriptor != null)
                            colUpdatedDescriptors.Add(oDescriptor);
                    }

                    nDefault = nEditable = nReadOnly = nHidden = 0;
                    mode = DisplayMode.Editable;
                    colMatchingBehaviors.Clear();
                }
                oPropertyInfo = null;
            }

            // little bit of clean up!
            if (colBehaviorCache != null)
                colBehaviorCache.Clear();
            if (colMatchingBehaviors != null)
                colMatchingBehaviors.Clear();
            colBehaviorCache = null;
            colMatchingBehaviors = null;

            return colUpdatedDescriptors;
        }

        /// <summary>
        /// Update the property descriptor with the new display mode
        /// </summary>
        /// <param name="oBehavior">the instance of the property behavior contains the display configurations.</param>
        /// <returns>the updated display mode</returns>
        private DisplayMode GetPropertyMode(PropertyBehaviorAttribute oBehavior)
        {
            // default display mode
            DisplayMode mode = DisplayMode.Default;

            // holds the number of the matching hidden behaviors
            int nHidden = 0;

            // holds the number of the matching readonly behaviors
            int nReadOnly = 0;

            // holds the number of the matching editable behaviors
            int nEditable = 0;

            // iterate through domain properties for a behavior match
            for (int index = 0; index < this.DomainProperties.Count; index++)
            {
                // overcome indexed reference
                DomainPropertyInfo oDependencyInfo = this.DomainProperties[index] as DomainPropertyInfo;
                // check the behavior match
                if (oBehavior.CompareDependency(oDependencyInfo, this.ModelElement))
                {
                    // if atleast one is hidden update the counter
                    // and break. hidden has the highest priority
                    if (oBehavior.DisplayMode == DisplayMode.Hidden)
                    {
                        nHidden++;
                        break;
                    }
                    // update the counter if behavior is readonly
                    else if (oBehavior.DisplayMode == DisplayMode.ReadOnly) nReadOnly++;
                    // update the counter if behavior is editable
                    else if (oBehavior.DisplayMode == DisplayMode.Editable) nEditable++;
                }

            }

            // set the display mode
            ///<remark>Priorities: Hidden = High; ReadOnly = Medium; Editable = Normal; None = Least.</remark>
            if (nHidden > 0)
            {
                mode = DisplayMode.Hidden;
            }
            else if (nReadOnly > 0)
            {
                mode = DisplayMode.ReadOnly;
            }
            else if (nEditable > 0)
            {
                mode = DisplayMode.Editable;
            }

            // return the new mode
            return mode;
        }

        /// <summary>
        /// Create the instance of the new property descriptor for the given display mode.
        /// </summary>
        /// <param name="oPropertyInfo">instance of the property info containing the property details.</param>
        /// <param name="displayMode">the display mode for the descriptor.</param>
        /// <returns>The instance of the ElementPropertyDescriptor.</returns>
        private ElementPropertyDescriptor CreateDescriptor(DomainPropertyInfo oPropertyInfo, DisplayMode displayMode)
        {
            // holds the return instance
            ElementPropertyDescriptor oPropertyDescriptor = null;

            // holds the attributes
            object[] arrAttributes = null;

            // temporarily stores the attributes
            List<Attribute> colAttributes = null;

            if (displayMode == DisplayMode.Hidden)
                return oPropertyDescriptor;

            // get the attributes
            arrAttributes = this.ModelElement.GetType().GetProperty(oPropertyInfo.Name
                , oPropertyInfo.PropertyType).GetCustomAttributes(typeof(Attribute), true);

            // create attribute collection
            colAttributes = new List<Attribute>();

            // if display mode is not default
            // add all the attributes other than ReadOnly and Browsable attributes.
            // else add all attributes
            if (arrAttributes != null && arrAttributes.Length > 0)
            {
                foreach (Attribute oAttribute in arrAttributes)
                {
                    if (displayMode == DisplayMode.Default
                        || !(oAttribute is ReadOnlyAttribute || oAttribute is BrowsableAttribute)
                        && !(displayMode == DisplayMode.ReadOnly && oAttribute is EditorAttribute))
                    {
                        colAttributes.Add(oAttribute);
                    }
                }
            }

            // set edit mode attributes
            if (displayMode == DisplayMode.ReadOnly)
                colAttributes.Add(new ReadOnlyAttribute(true));
            else if (displayMode == DisplayMode.Hidden)
                colAttributes.Add(new BrowsableAttribute(false));

            // create descriptor
            oPropertyDescriptor = this.CreatePropertyDescriptor(this.ModelElement, oPropertyInfo, colAttributes.ToArray());

            // clean up
            colAttributes.Clear();
            colAttributes = null;
            colAttributes = null;

            return oPropertyDescriptor;
        }

        /// <summary>
        /// Gets the type of the model standardValueProvider instance wrapped by the property descriptor.
        /// </summary>
        protected override Type ComponentType
        {
            get
            {
                return this.ModelElement != null ? this.ModelElement.GetType() : null;
            }
        }
    }
}
